﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using LinqExpression = System.Linq.Expressions.Expression;
using System.Text;
using Microsoft.Ajax.Utilities;
using System.Reflection.Emit;

namespace SimpleData.Infrastructure.JavaScript
{
	/// <summary>
	/// Auxilia na transformação do javascript para expressões linq
	/// </summary>
	public sealed class JavaScriptExpressionBuilder : TreeVisitor
	{
		private JavaScriptCodeContext context;
		private ModuleBuilder modBuilder;

		public JavaScriptExpressionBuilder()
		{
			this.context = null;
		}

		/// <summary>
		/// Obtém o código javascript transformado em expressões linq
		/// </summary>
		public JavaScriptCode Code
		{ get; private set; }

		/// <summary>
		/// Indica se essse é o contexto mais externo do código
		/// </summary>
		/// <returns></returns>
		public bool IsInRootContext()
		{
			return this.context != null && this.context.Parent == null;
		}

		public void SetModuleBuilder( ModuleBuilder modBuilder )
		{
			this.modBuilder = modBuilder;
		}

		public void Parse( Block code )
		{
			this.Visit( code );

			if ( this.IsInRootContext() )
				this.Code = new JavaScriptCode( this.context );
			else
				throw new InvalidOperationException();
		}

		public LinqExpression ParseExpression( AstNode node )
		{
			using ( var scope = this.CreateContext( "Expression Parser" ) )
			{
				node.Accept( this );
				return scope.Context.GetBlock();
			}
		}

		public override void Visit( Block node )
		{
			this.CreateContext( "block" );
			base.Visit( node );

			if ( !this.IsInRootContext() )
			{
				var block = this.PopContext().GetBlock();
				this.context.PushStatement( block );
			}
		}

		public override void Visit( ConstantWrapper node )
		{
			this.context.PushStatement( node.Cast() );
			base.Visit( node );
		}

		public override void Visit( FunctionObject node )
		{
			// Declara a função
			ExpressionSymbol symbol;

			if ( node.Identifier != null )
				symbol = this.context.Locals.Declare( node.Name, SymbolKind.Function );
			else
				symbol = this.context.Locals.Declare( "anonymous", SymbolKind.Function );

			// Declara os parâmetros da função
			foreach ( var parameter in node.ParameterDeclarations )
				symbol.DeclareParameter( LinqExpression.Parameter( typeof( JsObject ), parameter.Name ) );

			// Traduz o corpo da função
			base.Visit( node );

			// Cria a função
			symbol.Expression = LinqExpression.Lambda( this.context.GetBlock(), symbol.Parameters );
		}

		public override void Visit( Member node )
		{
			base.Visit( node );
		}

		public override void Visit( BinaryOperator node )
		{
			var leftExpression = this.ParseExpression( node.Operand1 );
			var rightExpression = this.ParseExpression( node.Operand2 );

			

			this.context.PushStatement( node.Cast( leftExpression, rightExpression ) );
		}

		public override void Visit( Lookup node )
		{
			base.Visit( node );

			switch ( node.RefType )
			{
				case ReferenceType.Constructor:
					break;
				case ReferenceType.Function:
					break;
				case ReferenceType.Variable:
					this.context.PushVarAccess( node.Name );
					break;
			}
		}

		public override void Visit( Conditional node )
		{
			base.Visit( node );
		}

		public override void Visit( ConstStatement node )
		{
			base.Visit( node );
		}

		public override void Visit( ArrayLiteral node )
		{
			base.Visit( node );
		}

		public override void Visit( ReturnNode node )
		{
			LinqExpression result;
			using ( var scope = this.CreateContext( "ReturnNode" ) )
			{
				// Realiza o parse do conteúdo
				base.Visit( node );

				// Obtém os comandos que serão executados
				result = scope.Context.GetBlock();
			}
			var resultType = result.GetReturnType();

			// Realiza a conversão para expressões 
			var label = LinqExpression.Label( resultType );
			this.context.PushStatement( LinqExpression.Return( label, result, resultType ) );
		}

		public override void Visit( Var node )
		{
			base.Visit( node );
		}

		public override void Visit( VariableDeclaration node )
		{
			base.Visit( node );
		}

		public override void Visit( CallNode node )
		{
			base.Visit( node );
		}

		public override void Visit( AstNodeList node )
		{
			base.Visit( node );
		}

		public override void Visit( UnaryOperator node )
		{
			base.Visit( node );
		}

		public override void Visit( CustomNode node )
		{
			base.Visit( node );
		}

		public override void Visit( GetterSetter node )
		{
			base.Visit( node );
		}

		public override void Visit( ThisLiteral node )
		{
			base.Visit( node );
		}

		public override void Visit( ObjectLiteral node )
		{
			base.Visit( node );
		}

		public override void Visit( LabeledStatement node )
		{
			bool isFunction = node.Statement is FunctionObject;

			if ( isFunction )
			{
				using ( var scope = this.CreateContext( "Label - " + node.Label ) )
				{
					base.Visit( node );

					// Obtém a função que foi declarada para o membro da classe JavaScript
					var function = scope.Context.Locals.GetFunctions().First();


				}
			}
			else
				base.Visit( node );
		}

		public override void Visit( ConstantWrapperPP node )
		{
			base.Visit( node );
		}

		public override void Visit( WithNode node )
		{
			base.Visit( node );
		}

		public override void Visit( ObjectLiteralField node )
		{
			base.Visit( node );
		}


		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		private PopJavaScriptContext CreateContext( string name )
		{
			this.context = new JavaScriptCodeContext( name, this.context );
			return new PopJavaScriptContext( this, this.context );
		}

		//private JavaScriptCodeContext CreateContext( SymbolTable globals)
		//{
		//    this.context = new JavaScriptCodeContext( this.context );
		//    this.context.Globals = globals;
		//}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		private JavaScriptCodeContext PopContext()
		{
			if ( this.context == null )
				throw new InvalidOperationException();

			try
			{
				return this.context;
			}
			finally
			{
				this.context = this.context.Parent;
			}
		}

		#region [ class PopJavaScriptContext ]

		private class PopJavaScriptContext : IDisposable
		{
			public PopJavaScriptContext( JavaScriptExpressionBuilder builder, JavaScriptCodeContext context )
			{
				this.Builder = builder;
				this.Context = context;
			}

			public JavaScriptExpressionBuilder Builder
			{
				get;
				private set;
			}

			public JavaScriptCodeContext Context
			{ get; private set; }

			#region IDisposable Members

			public void Dispose()
			{
				if ( this.Builder.context.Equals( this.Context ) )
					this.Builder.PopContext();
			}

			#endregion
		}

		#endregion
	}


	internal sealed class JavaScriptClassBuilder : TreeVisitor
	{
		private TypeBuilder typeBuilder;


		public JavaScriptClassBuilder( TypeBuilder typeBuilder, SymbolTable globals )
		{
			this.typeBuilder = typeBuilder;
		}

		public override void Visit( ObjectLiteral node )
		{
			//base.Visit( node );

		}
	}

	/// <summary>
	/// Realiza a promoção dos tipos de variáveis
	/// </summary>
	internal sealed class ExpressionTypePromoterVisitor : ExpressionVisitor
	{
		public ExpressionTypePromoterVisitor( Type promoteTo)
		{
			this.PromoteTo = promoteTo;
		}

		public Type PromoteTo
		{
			get;
			private set;
		}

		protected override LinqExpression VisitBinary( BinaryExpression node )
		{
			return base.VisitBinary( node );
		}
	}
}
